package com.glodon.paas.document.web.service;

import com.glodon.paas.security.oauth1.OAuthConsumerEx;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthProvider;
import oauth.signpost.exception.OAuthException;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.MessageException;
import org.openid4java.message.ax.FetchRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.List;

import static com.google.common.base.Strings.isNullOrEmpty;

/**
 * @author Don Li
 */
@Service
@Path("/sso/signin")
public class OpenidLoginService {

    private static final Logger LOGGER = LoggerFactory.getLogger(OpenidLoginService.class);

    @Value("${openid.discovery.url}")
    private String              discoveryUrl;

    @Value("${openid.callback}")
    private String              callbackUrl;

    @Value("${oauth.request.url}")
    private String              providerRequestUrl;

    @Value("${oauth.access.url}")
    private String              providerAccessUrl;

    @Value("${oauth.authorize.url}")
    private String              providerAuthorizeUrl;

    @Value("${oauth.consumer.key}")
    private String              consumerKey;

    @Value("${oauth.consumer.secret}")
    private String              consumerSecret;

    @Autowired
    private ConsumerManager     manager;

    @GET
    public Response autoLogin(@Context
    HttpServletRequest request) {
        return authenticate(request, "off", request.getParameter("userId"), request.getParameter("email"));
    }

    @POST
    public Response authenticate(@Context
    HttpServletRequest request, @FormParam("hybrid")
    @DefaultValue("off")
    String hybrid, @FormParam("userId")
    String userId, @FormParam("email")
    String email) {
        try {
            LOGGER.debug("start openid login, hybrid: {}", hybrid);

            final HttpSession session = request.getSession();

            String token = null;
            if ("on".equals(hybrid)) {
                OAuthConsumer consumer = new OAuthConsumerEx(consumerKey, consumerSecret);
                OAuthProvider provider = new DefaultOAuthProvider(providerRequestUrl, providerAccessUrl,
                                                                  providerAuthorizeUrl);
                try {
                    provider.retrieveRequestToken(consumer, callbackUrl);
                    token = consumer.getToken(); // oauth_token
                } catch (OAuthException e) {
                    LOGGER.error("fail to retrieve the request token and authorization url", e);
                }
                session.setAttribute("oauth-provider", provider);
                session.setAttribute("oauth-consumer", consumer);
            }

            List discoveries = manager.discover(discoveryUrl);
            DiscoveryInformation discovered = manager.associate(discoveries);
            session.setAttribute("openid-discovered", discovered);

            LOGGER.debug("openid service associated: {}", discovered);

            AuthRequest authRequest = manager.authenticate(discovered, callbackUrl);
            appendAtrributeFetchRequest(authRequest); // OpenID Attribute Exchange

            String authenticateUrl = buildAuthenticateRequestUrl(authRequest, hybrid, token, userId, email);

            LOGGER.debug("redirect to authenticate url: {}", authenticateUrl);

            return Response.temporaryRedirect(URI.create(authenticateUrl)).build(); // redirect user to authentication
                                                                                    // page
        } catch (DiscoveryException e) {
            LOGGER.error("discovery exception", e);
        } catch (MessageException e) {
            LOGGER.error("message exception", e);
        } catch (ConsumerException e) {
            LOGGER.error("consumer exception", e);
        }
        return null;
    }

    private String buildAuthenticateRequestUrl(AuthRequest authRequest, String hybrid, String token, String userId,
                                               String email) {
        // TODO use FORM POST redirect
        StringBuilder builder = new StringBuilder(authRequest.getDestinationUrl(true));
        builder.append("&service=document").append("&hybrid=").append(hybrid);
        if (!isNullOrEmpty(token)) {
            builder.append("&oauth_token=").append(token);
        }
        if (!isNullOrEmpty(userId)) {
            builder.append("&userId=").append(userId);
        }
        if (!isNullOrEmpty(email)) {
            builder.append("&email=").append(email);
        }
        return builder.toString();
    }

    private void appendAtrributeFetchRequest(AuthRequest authRequest) throws MessageException {
        FetchRequest fetchRequest = FetchRequest.createFetchRequest();
        // fetchRequest.addAttribute("email", true);
        // fetchRequest.addAttribute("username", true);
        authRequest.addExtension(fetchRequest);
    }
}
